#include "Xps/Protocol/TCP/Client.h"

namespace Xps
{
    namespace TCP
    {
        Client::Client(const std::string &_Name, const asio::ip::address &_Address, const short _Port, const IObjectFactory *_ObjectFactory )
            :name(_Name), address(_Address), port(_Port), is_running(false),object_factory(_ObjectFactory)
        {
            Initialize();
            
        }

        void Client::Start()
        {
            if(!is_running)
            {
                is_running = true;
                io_service = boost::shared_ptr<asio::io_service>(new asio::io_service());
                socket = boost::shared_ptr<tcp::socket>(new tcp::socket(*io_service));
                m_connector = boost::thread(&Client::Connect,this,address,port);
            }
        }

        void Client::Stop()
        {
            if(is_running)
            {
                is_running = false;
                io_service->stop();
                TerminateThread(m_connector.native_handle(),0);
                TerminateThread(m_reciever.native_handle(),0);
                TerminateThread(th_sender.native_handle(),0);
                TerminateThread(th_process_packets.native_handle(),0);
                socket->shutdown(tcp::socket::shutdown_both);
                socket->close();
            }
        }

        void Client::Recieve()
        {
            static byte byte_buffer[MAX_PACKET_SIZE];
            while(is_running)
            {
                memset(byte_buffer,0,MAX_PACKET_SIZE);
                try
                {
                    long length = socket->receive(asio::buffer(byte_buffer,MAX_PACKET_SIZE));
                    IObject::Ptr object = object_factory->FromBytes(byte_buffer, length);
                    incomming_queue.push(object);
                    SetEvent(recieve_event);
                }
                catch(...)
                {
                    Stop();
                }

            }
        }

        void Client::Send( IPacket::Ptr _Packet )
        {
            outgoing_queue.push(_Packet);
            SetEvent(sender_event);
        }

        void Client::ProcessPackets()
        {
            while(is_running)
            {
                WaitForSingleObject(recieve_event, INFINITE);
                for(int i=0,
                    size = incomming_queue.size();
                    i<size;i++)
                {
                    const IObject::Ptr object = incomming_queue.pop();
                    __raise OnPacketRecieved(name, object.get());

                }
            }
        }

        void Client::Sender()
        {
            static byte byte_buffer[MAX_PACKET_SIZE];
            while(is_running)
            {
                WaitForSingleObject(sender_event,INFINITE);
                for(int i=0,
                    size = outgoing_queue.size();
                    i<size;i++)
                {
                    IPacket::Ptr packet = outgoing_queue.pop();
                    try
                    {
                        memset(byte_buffer,0,MAX_PACKET_SIZE);
                        long length = object_factory->ToBytes(packet->getPayload(), byte_buffer,MAX_PACKET_SIZE);
                        socket->send(asio::buffer(byte_buffer,length));
                    }                 
                    catch(...)
                    {
                        Stop();
                    }

                }
            }
        }

        const std::string & Client::getName() const
        {
            return name;
        }

        Client::~Client( void )
        {
            Stop();
            CloseHandle(recieve_event);
            CloseHandle(sender_event);
        }

        void Client::Initialize()
        {
            recieve_event = CreateEvent(NULL, FALSE, FALSE, NULL);
            sender_event = CreateEvent(NULL, FALSE, FALSE, NULL);
        }

        void Client::Connect(const asio::ip::address &_Address, const short _Port)
        {
            boost::system::error_code ec;
            socket->connect(tcp::endpoint(_Address,_Port),ec);
            if(!ec)
            {
                m_reciever = boost::thread(&Client::Recieve,this);
                th_sender = boost::thread(&Client::Sender,this);
                th_process_packets = boost::thread(&Client::ProcessPackets, this);
            }
            else
            {
                Stop();
            }
        }
    };
};